 /*******************************************************************************
  * Copyright (c) 2005, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/

 package org.eclipse.osgi.internal.baseadaptor;

 import java.io.*;
 import java.lang.reflect.Method ;
 import java.net.URL ;
 import java.net.URLConnection ;
 import java.util.*;
 import org.eclipse.core.runtime.adaptor.LocationManager;
 import org.eclipse.osgi.baseadaptor.*;
 import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry;
 import org.eclipse.osgi.baseadaptor.bundlefile.ZipBundleFile;
 import org.eclipse.osgi.baseadaptor.hooks.AdaptorHook;
 import org.eclipse.osgi.baseadaptor.hooks.StorageHook;
 import org.eclipse.osgi.framework.adaptor.*;
 import org.eclipse.osgi.framework.debug.Debug;
 import org.eclipse.osgi.framework.internal.core.*;
 import org.eclipse.osgi.framework.internal.core.Constants;
 import org.eclipse.osgi.framework.log.FrameworkLog;
 import org.eclipse.osgi.framework.util.KeyedElement;
 import org.eclipse.osgi.service.datalocation.Location;
 import org.eclipse.osgi.util.ManifestElement;
 import org.eclipse.osgi.util.NLS;
 import org.osgi.framework.*;

 public class BaseStorageHook implements StorageHook, AdaptorHook{
     public static final String KEY = BaseStorageHook.class.getName();
     public static final int HASHCODE = KEY.hashCode();
     public static final int DEL_BUNDLE_STORE = 0x01;
     public static final int DEL_GENERATION = 0x02;
     private static final int STORAGE_VERSION = 1;
     public static final String EXTERNAL_LIB_PREFIX = "external:"; //$NON-NLS-1$
 public static final String VARIABLE_DELIM_STRING = "$"; //$NON-NLS-1$
 public static final char VARIABLE_DELIM_CHAR = '$';

     /** bundle's file name */
     private String fileName;
     /** native code paths for this BundleData */
     private String [] nativePaths;
     /** bundle generation */
     private int generation = 1;
     /** Is bundle a reference */
     private boolean reference;

     private BaseData bundleData;
     private BaseStorage storage;
     private File bundleStore;
     private File dataStore;

     public BaseStorageHook(BaseStorage storage) {
         this.storage = storage;
     }

     public int getStorageVersion() {
         return STORAGE_VERSION;
     }

     public StorageHook create(BaseData bundledata) throws BundleException {
         BaseStorageHook storageHook = new BaseStorageHook(storage);
         storageHook.bundleData = bundledata;
         return storageHook;
     }

     public void initialize(Dictionary manifest) throws BundleException {
         BaseStorageHook.loadManifest(bundleData, manifest);
     }

     static void loadManifest(BaseData target, Dictionary manifest) throws BundleException {
         try {
             target.setVersion(Version.parseVersion((String ) manifest.get(Constants.BUNDLE_VERSION)));
         } catch (IllegalArgumentException e) {
             target.setVersion(new InvalidVersion((String ) manifest.get(Constants.BUNDLE_VERSION)));
         }
         ManifestElement[] bsnHeader = ManifestElement.parseHeader(Constants.BUNDLE_SYMBOLICNAME, (String ) manifest.get(Constants.BUNDLE_SYMBOLICNAME));
         int bundleType = 0;
         if (bsnHeader != null) {
             target.setSymbolicName(bsnHeader[0].getValue());
             String singleton = bsnHeader[0].getDirective(Constants.SINGLETON_DIRECTIVE);
             if (singleton == null)
                 singleton = bsnHeader[0].getAttribute(Constants.SINGLETON_DIRECTIVE);
             if ("true".equals(singleton)) //$NON-NLS-1$
 bundleType |= BundleData.TYPE_SINGLETON;
         }
         target.setClassPathString((String ) manifest.get(Constants.BUNDLE_CLASSPATH));
         target.setActivator((String ) manifest.get(Constants.BUNDLE_ACTIVATOR));
         String host = (String ) manifest.get(Constants.FRAGMENT_HOST);
         if (host != null) {
             bundleType |= BundleData.TYPE_FRAGMENT;
             ManifestElement[] hostElement = ManifestElement.parseHeader(Constants.FRAGMENT_HOST, host);
             if (Constants.getInternalSymbolicName().equals(hostElement[0].getValue()) || Constants.OSGI_SYSTEM_BUNDLE.equals(hostElement[0].getValue())) {
                 String extensionType = hostElement[0].getDirective("extension"); //$NON-NLS-1$
 if (extensionType == null || extensionType.equals("framework")) //$NON-NLS-1$
 bundleType |= BundleData.TYPE_FRAMEWORK_EXTENSION;
                 else
                     bundleType |= BundleData.TYPE_BOOTCLASSPATH_EXTENSION;
             }
         }
         target.setType(bundleType);
         target.setExecutionEnvironment((String ) manifest.get(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT));
         target.setDynamicImports((String ) manifest.get(Constants.DYNAMICIMPORT_PACKAGE));
     }

     public StorageHook load(BaseData target, DataInputStream in) throws IOException {
         target.setLocation(AdaptorUtil.readString(in, false));
         target.setSymbolicName(AdaptorUtil.readString(in, false));
         target.setVersion(AdaptorUtil.loadVersion(in));
         target.setActivator(AdaptorUtil.readString(in, false));
         target.setClassPathString(AdaptorUtil.readString(in, false));
         target.setExecutionEnvironment(AdaptorUtil.readString(in, false));
         target.setDynamicImports(AdaptorUtil.readString(in, false));
         target.setStartLevel(in.readInt());
         target.setStatus(in.readInt());
         target.setType(in.readInt());
         target.setLastModified(in.readLong());
         target.setDirty(false); // make sure to reset the dirty bit;

         BaseStorageHook storageHook = new BaseStorageHook(storage);
         storageHook.bundleData = target;
         storageHook.generation = in.readInt();
         storageHook.reference = in.readBoolean();
         storageHook.setFileName(getAbsolute(storageHook.reference, AdaptorUtil.readString(in, false)));
         int nativePathCount = in.readInt();
         storageHook.nativePaths = nativePathCount > 0 ? new String [nativePathCount] : null;
         for (int i = 0; i < nativePathCount; i++)
             storageHook.nativePaths[i] = in.readUTF();
         return storageHook;
     }

     private String getAbsolute(boolean isReference, String path) {
         if (!isReference)
             return path;
         // fileName for bundles installed with reference URLs is stored relative to the install location
 File storedPath = new File(path);
         if (!storedPath.isAbsolute())
             // make sure it has the absolute location instead
 return new FilePath(storage.getInstallPath() + path).toString();
         return path;
     }

     public void save(DataOutputStream out) throws IOException {
         if (bundleData == null)
             throw new IllegalStateException ();
         AdaptorUtil.writeStringOrNull(out, bundleData.getLocation());
         AdaptorUtil.writeStringOrNull(out, bundleData.getSymbolicName());
         AdaptorUtil.writeStringOrNull(out, bundleData.getVersion().toString());
         AdaptorUtil.writeStringOrNull(out, bundleData.getActivator());
         AdaptorUtil.writeStringOrNull(out, bundleData.getClassPathString());
         AdaptorUtil.writeStringOrNull(out, bundleData.getExecutionEnvironment());
         AdaptorUtil.writeStringOrNull(out, bundleData.getDynamicImports());
         StorageHook[] hooks = bundleData.getStorageHooks();
         boolean forgetStartLevel = false;
         for (int i = 0; i < hooks.length && !forgetStartLevel; i++)
             forgetStartLevel = hooks[i].forgetStartLevelChange(bundleData.getStartLevel());
         out.writeInt(!forgetStartLevel ? bundleData.getStartLevel() : 1);
         boolean forgetStatus = false;
         // see if we should forget the persistently started flag
 for (int i = 0; i < hooks.length && !forgetStatus; i++)
             forgetStatus = hooks[i].forgetStatusChange(bundleData.getStatus());
         out.writeInt(!forgetStatus ? bundleData.getStatus() : (~Constants.BUNDLE_STARTED) & bundleData.getStatus());
         out.writeInt(bundleData.getType());
         out.writeLong(bundleData.getLastModified());

         out.writeInt(getGeneration());
         out.writeBoolean(isReference());
         String storedFileName = isReference() ? new FilePath(storage.getInstallPath()).makeRelative(new FilePath(getFileName())) : getFileName();
         AdaptorUtil.writeStringOrNull(out, storedFileName);
         if (nativePaths == null)
             out.writeInt(0);
         else {
             out.writeInt(nativePaths.length);
             for (int i = 0; i < nativePaths.length; i++)
                 out.writeUTF(nativePaths[i]);
         }

     }

     public int getKeyHashCode() {
         return HASHCODE;
     }

     public boolean compare(KeyedElement other) {
         return other.getKey() == KEY;
     }

     public Object getKey() {
         return KEY;
     }

     public String getFileName() {
         return fileName;
     }

     public int getGeneration() {
         return generation;
     }

     public String [] getNativePaths() {
         return nativePaths;
     }

     public void installNativePaths(String [] installPaths) throws BundleException {
         this.nativePaths = installPaths;
         for (int i = 0; i < installPaths.length; i++) {
             if (installPaths[i].startsWith(EXTERNAL_LIB_PREFIX)) {
                 String path = substituteVars(installPaths[i].substring(EXTERNAL_LIB_PREFIX.length()));
                 File nativeFile = new File(path);
                 if (!nativeFile.exists())
                     throw new BundleException(NLS.bind(AdaptorMsg.BUNDLE_NATIVECODE_EXCEPTION, nativeFile.getAbsolutePath()));
                 continue; // continue to next path
 }
             // ensure the file exists in the bundle; it will get extracted later on demand
 BundleEntry nativeEntry = bundleData.getBundleFile().getEntry(installPaths[i]);
             if (nativeEntry == null)
                 throw new BundleException(NLS.bind(AdaptorMsg.BUNDLE_NATIVECODE_EXCEPTION, installPaths[i]));
         }
     }

     public boolean isReference() {
         return reference;
     }

     public File getBundleStore() {
         if (bundleStore == null)
             bundleStore = new File(storage.getBundleStoreRoot(), String.valueOf(bundleData.getBundleID()));
         return bundleStore;
     }

     public File getDataFile(String path) {
         // lazily initialize dirData to prevent early access to configuration location
 if (dataStore == null)
             dataStore = new File(getBundleStore(), BaseStorage.DATA_DIR_NAME);
         if (path != null && !dataStore.exists() && (storage.isReadOnly() || !dataStore.mkdirs()))
             if (Debug.DEBUG && Debug.DEBUG_GENERAL)
                 Debug.println("Unable to create bundle data directory: " + dataStore.getPath()); //$NON-NLS-1$
 return path == null ? dataStore : new File(dataStore, path);
     }

     void delete(boolean postpone, int type) throws IOException {
         File delete = null;
         switch (type) {
             case DEL_GENERATION :
                 delete = getGenerationDir();
                 break;
             case DEL_BUNDLE_STORE :
                 delete = getBundleStore();
                 break;
         }
         if (delete != null && delete.exists() && (postpone || !AdaptorUtil.rm(delete))) {
             /* create .delete */
             FileOutputStream out = new FileOutputStream(new File(delete, BaseStorage.DELETE_FLAG));
             out.close();
         }
     }

     File getGenerationDir() {
         return new File(getBundleStore(), String.valueOf(getGeneration()));
     }

     File getParentGenerationDir() {
         Location parentConfiguration = null;
         Location currentConfiguration = LocationManager.getConfigurationLocation();
         if (currentConfiguration != null && (parentConfiguration = currentConfiguration.getParentLocation()) != null)
             return new File(parentConfiguration.getURL().getFile(), FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME + '/' + LocationManager.BUNDLES_DIR + '/' + bundleData.getBundleID() + '/' + getGeneration());
         return null;
     }

     File createGenerationDir() {
         File generationDir = getGenerationDir();
         if (!generationDir.exists() && (storage.isReadOnly() || !generationDir.mkdirs()))
             if (Debug.DEBUG && Debug.DEBUG_GENERAL)
                 Debug.println("Unable to create bundle generation directory: " + generationDir.getPath()); //$NON-NLS-1$
 return generationDir;
     }

     public void setReference(boolean reference) {
         this.reference = reference;
     }

     public void setFileName(String fileName) {
         this.fileName = fileName;
         // This is only done for PDE source lookup (bug 126517)
 this.bundleData.setFileName(fileName);
     }

     public void copy(StorageHook storageHook) {
         if (!(storageHook instanceof BaseStorageHook))
             throw new IllegalArgumentException ();
         BaseStorageHook hook = (BaseStorageHook) storageHook;
         bundleStore = hook.bundleStore;
         dataStore = hook.dataStore;
         generation = hook.generation + 1;
         // fileName and reference will be set by update
 }

     public void validate() throws IllegalArgumentException {
         // do nothing
 }

     public Dictionary getManifest(boolean firstLoad) throws BundleException {
         // do nothing
 return null;
     }

     public boolean forgetStatusChange(int status) {
         // do nothing
 return false;
     }

     public boolean forgetStartLevelChange(int startlevel) {
         // do nothing
 return false;
     }

     public boolean matchDNChain(String pattern) {
         // do nothing
 return false;
     }

     public void initialize(BaseAdaptor adaptor) {
         // do nothing
 }

     public void frameworkStart(BundleContext context) throws BundleException {
         // do nothing
 }

     public void frameworkStop(BundleContext context) throws BundleException {
         // shutdown the bundle file closer thread if it exists
 ZipBundleFile.shutdown();
     }

     public void frameworkStopping(BundleContext context) {
         // do nothing
 }

     public void addProperties(Properties properties) {
         // do nothing
 }

     public URLConnection mapLocationToURLConnection(String location) throws IOException {
         // see if this is an existing location
 Bundle[] bundles = storage.getAdaptor().getContext().getBundles();
         AbstractBundle bundle = null;
         for (int i = 0; i < bundles.length && bundle == null; i++)
             if (location.equals(bundles[i].getLocation()))
                 bundle = (AbstractBundle) bundles[i];
         if (bundle == null)
             return null;
         BaseData data = (BaseData) bundle.getBundleData();
         BaseStorageHook hook = (BaseStorageHook) data.getStorageHook(BaseStorageHook.KEY);
         return hook.isReference() ? new URL ("reference:file:" + hook.getFileName()).openConnection() : null; //$NON-NLS-1$
 }

     public void handleRuntimeError(Throwable error) {
         // do nothing
 }

     public boolean matchDNChain(String pattern, String [] dnChain) {
         // do nothing
 return false;
     }

     public FrameworkLog createFrameworkLog() {
         // do nothing
 return null;
     }

     public BaseStorage getStorage() {
         return storage;
     }

     public static String substituteVars(String path) {
         StringBuffer buf = new StringBuffer (path.length());
         StringTokenizer st = new StringTokenizer(path, VARIABLE_DELIM_STRING, true);
         boolean varStarted = false; // indicates we are processing a var subtitute
 String var = null; // the current var key
 while (st.hasMoreElements()) {
             String tok = st.nextToken();
             if (VARIABLE_DELIM_STRING.equals(tok)) {
                 if (!varStarted) {
                     varStarted = true; // we found the start of a var
 var = ""; //$NON-NLS-1$
 } else {
                     // we have found the end of a var
 String prop = null;
                     // get the value of the var from system properties
 if (var != null && var.length() > 0)
                         prop = FrameworkProperties.getProperty(var);
                     if (prop == null) {
                         try {
                             // try using the System.getenv method if it exists (bug 126921)
 Method getenv = System .class.getMethod("getenv", new Class [] {String .class}); //$NON-NLS-1$
 prop = (String ) getenv.invoke(null, new Object [] {var});
                         } catch (Throwable t) {
                             // do nothing;
 // on 1.4 VMs this throws an error
 // on J2ME this method does not exist
 }
                     }
                     if (prop != null)
                         // found a value; use it
 buf.append(prop);
                     else
                         // could not find a value append the var name w/o delims
 buf.append(var == null ? "" : var); //$NON-NLS-1$
 varStarted = false;
                     var = null;
                 }
             } else {
                 if (!varStarted)
                     buf.append(tok); // the token is not part of a var
 else
                     var = tok; // the token is the var key; save the key to process when we find the end token
 }
         }
         if (var != null)
             // found a case of $var at the end of the path with no trailing $; just append it as is.
 buf.append(VARIABLE_DELIM_CHAR).append(var);
         return buf.toString();
     }
 }

